package gotpsess

import (
	"net"
	"net/http"
	"sieve_engine/lib/config"
	"sieve_engine/lib/crypto/aes"
	"sieve_engine/lib/gotp"
	"sieve_engine/lib/json"
	"sieve_engine/lib/log"
	"time"
)

var (
	fPath    = config.String("lib.gotpsess.path", "/", "")
	fKey     = config.String("lib.gotpsess.key", "gotpsess", "")
	fSecret  = config.String("lib.gotpsess.secret", "", "")
	fExpire  = config.Int64("lib.gotpsess.expire", 0, "")
	fShowErr = config.Bool("lib.gotpsess.showerr", false, "")
)

type loginData struct {
	T int64  `json:"t,omitempty"`
	H string `json:"h,omitempty"`
	V string `json:"v,omitempty"`
}

func logErr(err error) {
	if *fShowErr {
		log.Err(err)
	}
}

func update(w http.ResponseWriter, login *loginData) bool {
	j, err := json.Marshal(login)
	if err != nil {
		logErr(err)
		return false
	}
	v, err := aes.EncryptToString([]byte(*fSecret), j)
	if err != nil {
		logErr(err)
		return false
	}
	now := time.Now()
	exp := now.AddDate(1, 0, 0)
	http.SetCookie(w, &http.Cookie{
		Path:     *fPath,
		Name:     *fKey,
		Value:    v,
		HttpOnly: true,
		Expires:  exp,
		MaxAge:   int(exp.Sub(now).Seconds()),
	})
	return true
}

func LoginSet(w http.ResponseWriter, r *http.Request, pwd string, val string) bool {
	otp, err := gotp.NewGoogleAuth(gotp.GenerateSecret([]byte(*fSecret)))
	if err != nil {
		logErr(err)
		return false
	}
	if pwd != otp.GenerateByTime() {
		return false
	}
	return update(w, &loginData{T: time.Now().Unix(), H: getHost(r.Host), V: val})
}

func Login(w http.ResponseWriter, r *http.Request, pwd string) bool {
	if *fSecret == "" {
		return true
	}
	return LoginSet(w, r, pwd, "")
}

func CheckGet(w http.ResponseWriter, r *http.Request) (bool, string) {
	ck, err := r.Cookie(*fKey)
	if err != nil {
		logErr(err)
		return false, ""
	}
	if ck.Value == "" {
		return false, ""
	}

	b, err := aes.DecryptString([]byte(*fSecret), ck.Value)
	if err != nil {
		logErr(err)
		return false, ""
	}

	login := &loginData{}
	if err := json.Unmarshal(b, &login); err != nil {
		logErr(err)
		return false, ""
	}
	if login.H != getHost(r.Host) {
		return false, ""
	}
	now := time.Now().Unix()
	if *fExpire > 0 && login.T+*fExpire < now {
		return false, ""
	}
	login.T = now
	return update(w, login), login.V
}

func Check(w http.ResponseWriter, r *http.Request) bool {
	if *fSecret == "" {
		return true
	}
	ok, _ := CheckGet(w, r)
	return ok
}

func getHost(s string) string {
	h, _, _ := net.SplitHostPort(s)
	return h
}
